package com.xiguthings.xiniu.iot.trigger.worker.trigger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xiguthings.xiniu.iot.common.DeviceData;
import com.xiguthings.xiniu.iot.common.DeviceDataBody;
import com.xiguthings.xiniu.iot.common.DeviceDataHead;
import com.xiguthings.xiniu.iot.common.entity.FieldExpr;
import com.xiguthings.xiniu.iot.common.entity.MessageProducerRule;
import com.xiguthings.xiniu.iot.common.entity.TriggerRule;
import com.xiguthings.xiniu.iot.etce.ProjectMessageEtcdClient;
import com.xiguthings.xiniu.iot.etce.TemplateEtcdClient;
import com.xiguthings.xiniu.iot.trigger.worker.controller.DataContrller;
import com.xiguthings.xiniu.iot.trigger.worker.trigger.calculation.impl.SimpleEngine;

@Component
public class Trigger {
	private final Logger LOGGER = LoggerFactory.getLogger(Trigger.class);

	public static final String DEVICE_ALERT = "DeviceAlert";
	public static final String DEVICE_RECOVERY = "DeviceRecovery";

	@Autowired
	private DataContrller dataContrller;
	@Autowired
	private TemplateEtcdClient templateEtcdClient;

	public static Map<String, Number> fieldLastStateMap = new Hashtable<>();// 记录某个字段上一条数据的值

	public static Map<String, SignRes> deviceLastStatus = new HashMap<>();// 记录设备上一次触发事件的状态，用于判断是告警触发还是恢复触发

	@Value("${trigger.thread.pool.size}")
	private int triggerThreadPoolSize;

	private ExecutorService triggerThreadPool;

	public Trigger() {
		init();
	}

	@PostConstruct
	private void init() {
		if (triggerThreadPoolSize == 0) {
			triggerThreadPoolSize = 5;
		}
		triggerThreadPool = Executors.newFixedThreadPool(triggerThreadPoolSize);
	}

	/**
	 * 激活触发,向kafka中添加告警消息 分发消息的方式改变了，需要分topic和partition，所有废弃此代码
	 * 
	 * @param message
	 */
	// private void activateAlarm(String message) {
	// if (StringUtils.isBlank(message)) {
	// return;
	// }
	// dataContrller.producerMessage(message);
	//
	// }

	/**
	 * 激活触发,向kafka中添加告警消息
	 *
	 * @param message
	 */
	private void activateAlarm(String value, String topic, Integer partition) {
		LOGGER.debug("发送到kafka：topic:{},partition:{}", topic, partition);
		if (StringUtils.isBlank(value) || StringUtils.isBlank(topic) || partition == null) {
			return;
		}
		dataContrller.producerMessage(value, topic, partition);

	}

	/**
	 * 进行检查设备数据，并在实现中调用activateAlarm(String message) 方法进行处罚告警
	 *
	 * @param deviceData
	 */
	public void checkDeviceData(DeviceData deviceData, List<TriggerRule> rules) {
		triggerThreadPool.execute(new Runnable() {
			@Override
			public void run() {
				checkDataByRules(deviceData, rules);
			}
		});
	}

	/**
	 * 检查数据和触发消息
	 *
	 * @param deviceData
	 * @param rules
	 */
	private void checkDataByRules(DeviceData deviceData, List<TriggerRule> rules) {
		for (TriggerRule rule : rules) {// 多个规则之间是 或 的关系
			Event event = checkFieldsValue(deviceData, rule);
			// System.out.println(event);
			if (event.touch == true) {
				JSONObject makeMessage = makeMessage(deviceData, rule, event.name);
				String message = makeMessage.toJSONString();
				LOGGER.debug("触发告警，生产的消息  message:{}", message);
				String topic = null;
				Integer partition = null;
				MessageProducerRule messageProducerRule = ProjectMessageEtcdClient
						.messageProducerRuleByProjectId(deviceData.getHead().getProjectId());
				if (messageProducerRule == null) {
					LOGGER.info("该条触发事件，没有发送目的:{}", message);
					continue;
				}
				topic = messageProducerRule.getTopic();
				partition = new Integer(messageProducerRule.getPartition());
				if (StringUtils.isBlank(message) || StringUtils.isBlank(topic) || partition == null) {
					continue;
				}
				activateAlarm(message, topic, partition);
			}
		}
	}

	/**
	 * 生成消息
	 * 
	 * @param deviceData
	 * @param rule
	 * @param resStr
	 * @return
	 */
	private JSONObject makeMessage(DeviceData deviceData, TriggerRule rule, String name) {
		JSONObject message = new JSONObject();
		message.put("rule", rule.getRuleId());
		message.put("ruleName", rule.getRuleId());
		message.put("name", name);
		message.put("time", ((long) (System.currentTimeMillis() * 0.001)));
		message.put("serverity", rule.getServerity());
		message.put("producer", "iot-trigger");
		JSONObject data = new JSONObject();
		data.put("tags", deviceData.getHead());
		data.put("data", deviceData.getBody());
		data.put("expression", rule.getExpr());
		message.put("body", data);
		// 拼接msg字段
		String msgString = rule.getMsg();
		StringBuilder msgsb = new StringBuilder(msgString);

		while (true) {
			int indexOf = msgsb.indexOf("{{");
			int indexOf2 = msgsb.indexOf("}}", indexOf);
			if (indexOf == -1 || indexOf2 == -1) {
				break;
			}
			indexOf2 += 2;

			String substring = msgsb.substring(indexOf, indexOf2);
			String fieldName = substring.replace("{{", "").replace("}}", "");
			Object value = deviceData.getBody().get(fieldName);
			if (value == null) {
				value = deviceData.getHead().get(fieldName);
			}
			msgsb.replace(indexOf, indexOf2, value + "");
		}
		message.put("msg", msgsb.toString());
		return message;
	}

	/**
	 * 检查规则内指定的所有字段是否符合条件
	 *
	 * @param exprMap
	 * @param body
	 * @return
	 */
	private Event checkFieldsValue(DeviceData deviceData, TriggerRule triggerRule) {

		String rp = triggerRule.getRp();
		Map<String, FieldExpr> exprMap = triggerRule.getExpr();
		DeviceDataBody body = deviceData.getBody();

		// 先设置默认值
		if (StringUtils.isBlank(rp)) {
			rp = "AND";
		}
		Set<Map.Entry<String, FieldExpr>> exprEntrySet = exprMap.entrySet();

		// 判断是否是老版本的rp引擎，需要做老版本,兼容的包括（ null 、"" 、or 、and）
		if ("OR".equals(rp) || "AND".equals(rp) || "or".equals(rp) || "and".equals(rp)) {
			String tempRp = " " + rp + " ";
			List<String> fieldNames = new ArrayList<>();
			for (Map.Entry<String, FieldExpr> entry : exprEntrySet) {
				fieldNames.add(entry.getKey());
			}
			rp = String.join(tempRp, fieldNames);
		}

		String finalRp = rp;
		Map<String, SignRes> fieldsCalculation = fieldsCalculation(deviceData, body, exprEntrySet);
		// 开始组合成最后的表达式
		for (Map.Entry<String, SignRes> entry : fieldsCalculation.entrySet()) {
			finalRp = finalRp.replace(entry.getKey(), new Boolean(entry.getValue().trig).toString());
		}
		SimpleEngine simpleEngine = new SimpleEngine();
		// 将字段的计算后的结果，放进计算引擎中计算
		boolean b = false;
		try {
			b = simpleEngine.expressionCalculation(finalRp);
		} catch (Exception e) {
			LOGGER.info("多个字段表达式计算错误：{},表达式：{}", e.getMessage(), finalRp);
			return new Event();
		}
		// 是否需要重复触发的标记
		boolean isMulti = triggerRule.getMulti_alert() == 0 ? false : true;

		Event event = new Event();
		if (isMulti) {
			// 如果需要重复告警，则直接返回触发结果
			event.touch = b;
		}
		if (isMulti == false) {
			String deviceLastStatusKey = deviceData.getHead().getDeviceId() + triggerRule.getRuleId();
			// 如果不需要重复告警
			SignRes lastSignRes = deviceLastStatus.get(deviceLastStatusKey);
			if (lastSignRes == null) {
				// 如果之前没有状态，则默认为之前是正常的状态
				lastSignRes = new SignRes();
				lastSignRes.trig = false;
				deviceLastStatus.put(deviceLastStatusKey, lastSignRes);
			}

			if (lastSignRes.trig == b) {
				// 如果状态和之前触发的时间状态一致，则不需要再发送事件，所以直接返回结果
				return new Event();
			}

			// 将本次触发事件的对象保存起来
			SignRes signRes = new SignRes();
			signRes.trig = b;
			deviceLastStatus.put(deviceLastStatusKey, signRes);

			// 赋值是否触发
			event.touch = true;
		}
		if (b == false) {
			// 触发了恢复事件
			event.name = DEVICE_RECOVERY;
		}
		if (b == true) {
			// 触发了告警事件
			event.name = DEVICE_ALERT;
		}
		return event;
	}

	/**
	 * 计算所有字段的的值，返回多个结果
	 * 
	 * @param deviceData
	 * @param body
	 * @param exprEntrySet
	 * @return
	 */
	private Map<String, SignRes> fieldsCalculation(DeviceData deviceData, DeviceDataBody body,
			Set<Map.Entry<String, FieldExpr>> exprEntrySet) {
		Map<String, SignRes> res = new HashMap<>();
		for (Map.Entry<String, FieldExpr> exprEntry : exprEntrySet) {
			String fieldName = exprEntry.getKey();
			Object value = body.get(fieldName);
			Number fieldValue = (Number) value;
			FieldExpr expr = exprEntry.getValue();
			SignRes fieldCheckExpr = fieldCheckExpr(deviceData, fieldName, fieldValue, expr);
			res.put(fieldName, fieldCheckExpr);
		}
		return res;
	}

	/**
	 * 检查一个字段中 所有需要判断的 表达式
	 *
	 * @param fieldValue
	 * @param expr
	 * @return
	 */
	private SignRes fieldCheckExpr(DeviceData deviceData, String fieldName, Number fieldValue, FieldExpr fieldExpr) {
		// 创建用于保存字段中每个表达式计算结果的集合
		Map<String, SignRes> signResMap = new HashMap<>();

		// 获取表达式之间的关系
		String rp = fieldExpr.getRp();
		// 获取表达式集合
		List<Map<String, Number>> sins = fieldExpr.getSins();
		// 计算每单个表达式的结果
		for (int i = 0; i < sins.size(); i++) {
			Map<String, Number> sin = sins.get(i);
			SignRes signRes = new SignRes();
			try {
				String signKey = sin.keySet().iterator().next();
				Number value = sin.values().iterator().next();
				SignRes fieldCheckSign = fieldCheckSign(deviceData, fieldName, fieldValue, signKey, value);
				signResMap.put("" + i, fieldCheckSign);
			} catch (java.lang.ClassCastException e) {
				LOGGER.info("获取规则参数失败，参数错误:{}", e.getMessage());
				return signRes;
			}
		}

		// 将rp中的表达式中添加结果
		Set<Entry<String, SignRes>> entrySet = signResMap.entrySet();
		for (Entry<String, SignRes> entry : entrySet) {
			rp = rp.replace(entry.getKey(), entry.getValue().trig + "");
		}
		// 用计算引擎进行计算，得出最终结果
		SimpleEngine simpleEngine = new SimpleEngine();
		boolean expBoolenValue = false;
		try {
			expBoolenValue = simpleEngine.expressionCalculation(rp);
		} catch (Exception e) {
			LOGGER.info("字段中计算表达式计算错误：{},表达式：{}", e.getMessage(), rp);
		}
		SignRes signRes = new SignRes();
		signRes.trig = expBoolenValue;
		return signRes;
	}

	/**
	 * 提供表达式符号和值 对字段的值 进行 判断
	 *
	 * @param fieldValue
	 * @param sign
	 * @param value
	 * @return
	 */
	private SignRes fieldCheckSign(DeviceData deviceData, String filedName, Number fieldValue, String sign,
			Number value) {
		SignRes signRes = new SignRes();
		if (value == null || fieldValue == null) {
			return signRes;
		}
		switch (sign) {
		case FieldExpr.EQ: {
			signRes.trig = fieldValue.equals(value);
			break;
		}
		case FieldExpr.NEQ: {
			signRes.trig = !fieldValue.equals(value);
			break;
		}
		case FieldExpr.LT: {
			signRes.trig = fieldValue.doubleValue() < value.doubleValue();
			break;
		}
		case FieldExpr.GT: {
			signRes.trig = fieldValue.doubleValue() > value.doubleValue();
			break;
		}

		case FieldExpr.GEQ: {
			signRes.trig = fieldValue.doubleValue() >= value.doubleValue();
			break;
		}
		case FieldExpr.LEQ: {
			signRes.trig = fieldValue.doubleValue() <= value.doubleValue();
			break;
		}

		case FieldExpr.LT_DELTA: {
			Number lastValue = fieldLastStateMap.get(deviceData.getHead().getDeviceId() + filedName);
			if (lastValue == null) {
				signRes.trig = false;
			} else {
				int val = 0;
				if (fieldValue instanceof Long) {
					val = (int) (fieldValue.longValue() - lastValue.longValue());
				} else {
					val = (int) (fieldValue.doubleValue() - lastValue.doubleValue());
				}
				if (Math.abs(val) >= Math.abs(value.intValue())) {
					signRes.trig = true;
					if (val < 0) {
						signRes.trig = true;
					}
				}
			}
			fieldLastStateMap.put(deviceData.getHead().getDeviceId() + filedName, fieldValue);
			break;
		}
		case FieldExpr.GT_DELTA: {
			Number lastValue = fieldLastStateMap.get(deviceData.getHead().getDeviceId() + filedName);
			if (lastValue == null) {
				signRes.trig = false;
			} else {
				int val = 0;
				if (fieldValue instanceof Long) {
					val = (int) (fieldValue.longValue() - lastValue.longValue());

				} else {
					val = (int) (fieldValue.doubleValue() - lastValue.doubleValue());
				}
				if (Math.abs(val) >= Math.abs(value.intValue())) {
					if (val > 0) {
						signRes.trig = true;
					}
				}
			}
			fieldLastStateMap.put(deviceData.getHead().getDeviceId() + filedName, fieldValue);
			break;
		}
		default: {
		}

		}
		return signRes;
	}

	class Event {
		String name;
		boolean touch;

		@Override
		public String toString() {
			return "Event [name=" + name + ", touch=" + touch + "]";
		}
	}

	// 测试
	public static void main(String[] args) throws InterruptedException {
		Trigger trigger = new Trigger();
		List<TriggerRule> rules = new ArrayList<>();
		// 创建规则
		{
			String ruleStr = "{\"expr\":{\"uncap\":{\"sins\":[{\"gt\":0},{\"leq\":3}],\"rp\":\"0 AND 1\"},\"xValue\":{\"sins\":[{\"gt\":0},{\"leq\":3}],\"rp\":\"0 AND 1\"}},\"rp\":\"AND\",\"modelId\":\"9e9cc553-f4d3-48d7-9926-041284309339\",\"includeIds\":[1],\"excludeIds\":[],\"serverity\":\"Warning\",\"templateId\":\"templateId-1\",\"msg\":\"我想看看字段的值，{uncap：{{uncap}},xValue:{{xValue}}}\",\"name\":\"trigger3\",\"multi_alert\":1}";
			TriggerRule triggerRule = JSON.parseObject(ruleStr, TriggerRule.class);
			rules.add(triggerRule);
		}
		// 创建数据，并检查触发
		{
			DeviceData deviceData = new DeviceData();
			DeviceDataHead head = new DeviceDataHead();
			head.setDeviceId(1L);
			deviceData.setHead(head);
			DeviceDataBody body = new DeviceDataBody();
			body.put("uncap", 3);
			body.put("xValue", 3);
			deviceData.setBody(body);
			trigger.checkDeviceData(deviceData, rules);
		}

	}
}
